<template>
	<div class="main">
		<md-tabs class="md-primary main-menu" md-elevation="1" md-active-tab="tab-rule-list">
			<md-tab id="tab-rule-list" :md-label="t('rule_list')">
				<div class="loading-box" v-show="isLoadingRules">
					<md-progress-spinner md-mode="indeterminate" :md-stroke="4"></md-progress-spinner>
				</div>
				<md-card v-for="g of group" :key="g.name" class="group-item" v-show="!isLoadingRules && Object.keys(g.rule).length > 0">
					<md-card-header>
						<div class="md-title">{{g.name}}</div>
						<md-button class="md-icon-button" @click="g.collapse = !g.collapse">
							<md-icon v-show="!g.collapse" class="iconfont icon-keyboard-arrow-up"></md-icon>
							<md-icon v-show="g.collapse" class="iconfont icon-keyboard-arrow-down"></md-icon>
						</md-button>
						<md-menu md-size="big" md-direction="bottom-end">
							<md-button class="md-icon-button" md-menu-trigger>
								<md-icon class="iconfont icon-more-vert"></md-icon>
							</md-button>
							<md-menu-content>
								<md-menu-item @click="onGroupRename(g)">
									<span>{{t('rename')}}</span>
									<md-icon class="iconfont icon-edit"></md-icon>
								</md-menu-item>
								<md-menu-item @click="onGroupShare(g.name)">
									<span>{{t('share')}}</span>
									<md-icon class="iconfont icon-share"></md-icon>
								</md-menu-item>
								<md-menu-item @click="onGroupDelete(g.name)">
									<span>{{t('delete')}}</span>
									<md-icon class="iconfont icon-delete"></md-icon>
								</md-menu-item>
							</md-menu-content>
						</md-menu>
					</md-card-header>
					<md-card-content>
						<md-table v-show="!g.collapse">
							<md-table-row>
								<md-table-head class="cell-batch"><span @click="onBatchSelectGroup(g)" style="cursor: pointer">{{t('batch_mode')}}</span></md-table-head>
								<md-table-head class="cell-enable">{{t('enable')}}</md-table-head>
								<md-table-head class="cell-name">{{t('name')}}</md-table-head>
								<md-table-head class="cell-type">{{t('ruleType')}}</md-table-head>
								<md-table-head class="cell-action">{{t('action')}}</md-table-head>
							</md-table-row>
							<md-table-row v-for="r of g.rule" :key="r._v_key">
								<md-table-cell class="cell-batch">
									<md-checkbox v-model="batch" :value="r" class="md-primary"></md-checkbox>
								</md-table-cell>
								<md-table-cell class="cell-enable">
									<md-switch v-model="r.enable" class="md-primary" :data-type="r.ruleType" :data-id="r.id" @change="newValue => onRuleEnable(r, newValue)"></md-switch>
								</md-table-cell>
								<md-table-cell class="cell-name">
									<span>{{r.name}}</span>
									<md-tooltip md-direction="bottom">
										<p>{{t('matchType')}}: {{t('match_' + r.matchType)}}</p>
										<p>{{t('matchRule')}}: {{r.pattern}}</p>
										<p>{{t('exec_type')}}: {{t('exec_' + (r.isFunction ? 'function' : 'normal'))}}</p>
										<p v-if="r.ruleType === 'redirect'">{{t('redirectTo')}}: {{r.to}}</p>
										<p v-if="r.ruleType === 'modifyReceiveBody'">{{t('encoding')}}: {{r.encoding || encodingsList[0]}}</p>
										<p v-if="(r.ruleType === 'modifySendHeader' || r.ruleType === 'modifyReceiveHeader') && !r.isFunction">{{t('headerName')}}: {{r.action.name}}</p>
										<p v-if="(r.ruleType === 'modifySendHeader' || r.ruleType === 'modifyReceiveHeader') && !r.isFunction">{{t('headerValue')}}: {{r.action.value}}</p>
									</md-tooltip>
								</md-table-cell>
								<md-table-cell class="cell-type">{{t('rule_' + r.ruleType)}}</md-table-cell>
								<md-table-cell class="cell-action">
									<md-button class="with-icon group-button" @click="onChangeRuleGroup(r)"><md-icon class="iconfont icon-playlist-add"></md-icon><span>{{t('group')}}</span></md-button>
									<md-button class="with-icon edit-button" @click="onEditRule(r)" :disabled="!isSupportStreamFilter && r.ruleType === 'modifyReceiveBody'"><md-icon class="iconfont icon-edit"></md-icon><span>{{t('edit')}}</span></md-button>
									<md-button class="with-icon clone-button" @click="onCloneRule(r)"><md-icon class="iconfont icon-content-copy"></md-icon><span>{{t('clone')}}</span></md-button>
									<md-button class="with-icon view-button" @click="onViewRule(r)"><md-icon class="iconfont icon-search"></md-icon><span>{{t('view')}}</span></md-button>
									<md-button class="with-icon delete-button" @click="onRemoveRule(r)"><md-icon class="iconfont icon-delete"></md-icon><span>{{t('delete')}}</span></md-button>
								</md-table-cell>
							</md-table-row>
						</md-table>
					</md-card-content>
				</md-card>
			</md-tab>
			<md-tab id="tab-options" :md-label="t('options')">
				<md-card>
					<md-card-header>
						<div class="md-title">{{t('options')}}</div>
					</md-card-header>
					<md-card-content>
						<div class="md-layout md-gutter">
							<div class="md-layout-item md-size-50 md-small-size-100"><md-checkbox v-model="options['manage-collapse-group']">{{t('manage_collapse_group')}}</md-checkbox></div>
							<div class="md-layout-item md-size-50 md-small-size-100"><md-checkbox v-model="options['exclude-he']">{{t('rules_no_effect_for_he')}}</md-checkbox></div>
							<div class="md-layout-item md-size-50 md-small-size-100"><md-checkbox v-model="options['add-hot-link']">{{t('add_anti_hot_link_to_menu')}}</md-checkbox></div>
							<div class="md-layout-item md-size-50 md-small-size-100"><md-checkbox v-model="options['show-common-header']">{{t('display_common_header')}}</md-checkbox></div>
							<div class="md-layout-item md-size-50 md-small-size-100"><md-checkbox v-model="options['include-headers']">{{t('include_header_in_custom_function')}}</md-checkbox></div>
							<div class="md-layout-item md-size-50 md-small-size-100"><md-checkbox v-model="options['modify-body']">{{t('modify_body')}}</md-checkbox></div>
						</div>
					</md-card-content>
				</md-card>
			</md-tab>
			<md-tab id="tab-export-import" :md-label="t('export_and_import')">
				<md-button class="with-icon" @click="onExportAll"><md-icon class="iconfont icon-save"></md-icon>{{t('export')}}</md-button>
				<md-button class="with-icon" @click="onImport"><md-icon class="iconfont icon-folder-open"></md-icon>{{t('import')}}</md-button>
				<md-button class="with-icon" @click="cloud.show = true"><md-icon class="iconfont icon-cloud"></md-icon>{{t('cloud_backup')}}</md-button>
				<md-card>
					<md-card-header>
						<div class="md-title">{{t('download_rule')}}</div>
					</md-card-header>
					<md-card-content>
						<div class="download-input">
							<md-field md-inline class="url">
								<label>URL</label>
								<md-input v-model="download.url"></md-input>
							</md-field>
							<md-button class="md-icon-button" @click="onDownloadClick"><md-icon class="iconfont icon-file-download"></md-icon></md-button>
							<md-button class="md-icon-button" :title="t('third_party_rules')" @click="onOpenThirdParty"><md-icon class="iconfont icon-search"></md-icon></md-button>
						</div>
						<md-list class="download-list">
							<md-list-item v-for="url of download.log" :key="url">
								<span class="md-list-item-text">{{url}}</span>
								<md-button class="md-icon-button md-list-action" @click="onDownloadLogClick(url)" :title="t('download')"><md-icon class="iconfont icon-file-download"></md-icon></md-button>
								<md-button class="md-icon-button md-list-action" @click="download.url = url" :title="t('edit')"><md-icon class="iconfont icon-edit"></md-icon></md-button>
								<md-button class="md-icon-button md-list-action" @click="download.log.splice(download.log.indexOf(url), 1)" :title="t('delete')"><md-icon class="iconfont icon-delete"></md-icon></md-button>
							</md-list-item>
						</md-list>
					</md-card-content>
				</md-card>
				<!-- import list -->
				<md-card class="import-confirm">
					<md-card-area>
						<md-card-header>
							<div class="md-title">{{t('import')}}</div>
						</md-card-header>
						<md-card-content>
							<md-progress-bar md-mode="indeterminate" v-show="imports.status == 1"></md-progress-bar>
							<md-table v-show="imports.status == 2" class="import-table">
								<md-table-row>
									<md-table-head class="cell-name">{{t('name')}}</md-table-head>
									<md-table-head class="cell-type">{{t('ruleType')}}</md-table-head>
									<md-table-head class="cell-group">{{t('suggested_group')}}</md-table-head>
									<md-table-head class="cell-action">{{t('action')}}</md-table-head>
								</md-table-row>
								<md-table-row v-for="r of imports.list" :key="r.id">
									<md-table-cell class="cell-name">{{r.name}}</md-table-cell>
									<md-table-cell class="cell-type">{{t('rule_' + r.ruleType)}}</md-table-cell>
									<md-table-cell class="cell-group">
										<span>{{r.group}}</span>
										<md-button class="md-primary" @click="onImportRuleChooseGroup(r)">{{t('choose')}}</md-button>
									</md-table-cell>
									<md-table-cell class="cell-action">
										<md-radio class="md-primary" v-model="r.import_action" :value="1">{{t('import_new')}}</md-radio>
										<md-radio class="md-primary" v-model="r.import_action" :value="2" v-show="r.import_old_id">{{t('import_override')}}</md-radio>
										<md-radio class="md-primary" v-model="r.import_action" :value="3">{{t('import_drop')}}</md-radio>
									</md-table-cell>
								</md-table-row>
							</md-table>
						</md-card-content>
					</md-card-area>
					<md-card-actions md-alignment="left" v-show="imports.status == 2">
						<div class="save-to">
							<span>{{t('save_to')}}</span>
							<md-radio class="md-primary" v-model="imports.group_type" :value="0">{{imports.group_name}}<md-button class="md-primary" @click="onImportChooseGroup">{{t('choose')}}</md-button></md-radio>
							<md-radio class="md-primary" v-model="imports.group_type" :value="1">{{t('suggested_group')}}</md-radio>
						</div>
						<md-button @click="onImportSave">{{t('save')}}</md-button>
						<md-button @click="imports.status = 0">{{t('cancel')}}</md-button>
					</md-card-actions>
				</md-card>
			</md-tab>
			<md-tab id="tab-help" :md-label="t('help')">
				<md-button @click="onOpenHelp" class="with-icon"><md-icon class="iconfont icon-open-in-new"></md-icon>{{t('view')}}</md-button>
			</md-tab>
		</md-tabs>
		<div id="edit-page" v-show="isShowEdit">
			<md-toolbar class="md-primary" md-elevation="1">
				<md-button class="md-icon-button" @click="closeEditPage">
					<md-icon class="iconfont icon-arrow-back"></md-icon>
				</md-button>
				<h2 class="md-title" style="flex: 1">{{editTitle}}</h2>
			</md-toolbar>
			<div class="md-layout md-gutter">
				<div class="md-layout-item md-size-70 md-small-size-100">
					<md-card>
						<md-card-header>
							<md-card-header-text>
								<div class="md-title">{{editTitle}}</div>
							</md-card-header-text>
						</md-card-header>
						<md-card-content>
							<md-field>
								<label for="rule-name">{{t('name')}}</label>
								<md-input id="rule-name" v-model="edit.name" />
							</md-field>
							<div class="form-group">
								<div class="left">{{t('ruleType')}}</div>
								<div class="right">
									<md-radio class="md-primary" v-model="edit.ruleType" value="cancel" :disabled="!edit.ruleTypeEditable">{{t('rule_cancel')}}</md-radio>
									<md-radio class="md-primary" v-model="edit.ruleType" value="redirect" :disabled="!edit.ruleTypeEditable">{{t('rule_redirect')}}</md-radio>
									<md-radio class="md-primary" v-model="edit.ruleType" value="modifySendHeader"	:disabled="!edit.ruleTypeEditable">{{t('rule_modifySendHeader')}}</md-radio>
									<md-radio class="md-primary" v-model="edit.ruleType" value="modifyReceiveHeader"	:disabled="!edit.ruleTypeEditable">{{t('rule_modifyReceiveHeader')}}</md-radio>
									<md-radio class="md-primary" v-model="edit.ruleType" value="modifyReceiveBody"	:disabled="!edit.ruleTypeEditable || !isSupportStreamFilter">{{t('rule_modifyReceiveBody')}}</md-radio>
								</div>
							</div>
							<div class="form-group">
								<div class="left">{{t('matchType')}}</div>
								<div class="right">
									<md-radio class="md-primary" v-model="edit.matchType" value="all">{{t('match_all')}}</md-radio>
									<md-radio class="md-primary" v-model="edit.matchType" value="regexp">{{t('match_regexp')}}</md-radio>
									<md-radio class="md-primary" v-model="edit.matchType" value="prefix">{{t('match_prefix')}}</md-radio>
									<md-radio class="md-primary" v-model="edit.matchType" value="domain">{{t('match_domain')}}</md-radio>
									<md-radio class="md-primary" v-model="edit.matchType" value="url">{{t('match_url')}}</md-radio>
								</div>
							</div>
							<!-- Rule match rule -->
							<md-field v-show="edit.matchType != 'all'">
								<label for="rule-matchRule">{{t('matchRule')}}</label>
								<md-input id="rule-matchRule" v-model="edit.matchRule" />
							</md-field>
							<!-- Rule exclude rule -->
							<md-field v-show="edit.matchType != 'all'">
								<label for="rule-excludeRule">{{t('excludeRule')}}</label>
								<md-input id="rule-excludeRule" v-model="edit.excludeRule" />
							</md-field>
							<!-- Response body encoding -->
							<div class="form-group" v-if="edit.ruleType === 'modifyReceiveBody'">
								<div class="left">{{t('encoding')}}</div>
								<div class="right">
									<md-field>
										<md-select id="rule-encoding" v-model="edit.encoding">
											<md-option v-for="option of encodingsList" :key="option" :value="option">{{option}}</md-option>
										</md-select>
									</md-field>
								</div>
							</div>
							<!-- isFunction or not -->
							<div class="form-group">
								<div class="left">{{t('exec_type')}}</div>
								<div class="right">
									<md-radio class="md-primary" v-model="edit.execType" :value="0"
										:disabled="edit.ruleType === 'modifyReceiveBody'"
									>{{t('exec_normal')}}</md-radio>
									<md-radio class="md-primary" v-model="edit.execType" :value="1"
										:disabled="edit.ruleType === 'modifyReceiveBody'"
									>{{t('exec_function')}}</md-radio>
								</div>
							</div>
							<!-- redirect to -->
							<md-field v-show="edit.ruleType == 'redirect' && edit.execType == 0">
								<label for="rule-redirectTo">{{t('redirectTo')}}</label>
								<md-input id="rule-redirectTo" v-model="edit.redirectTo" />
							</md-field>
							<!-- header mondify -->
							<div v-if="(edit.ruleType == 'modifySendHeader' || edit.ruleType == 'modifyReceiveHeader') && edit.execType == 0">
								<md-autocomplete v-model="edit.headerName" :md-options="edit.ruleType == 'modifySendHeader' ? commonHeader.request : commonHeader.response" :md-open-on-focus="false">
									<label for="rule-headerName">{{t('headerName')}}</label>
								</md-autocomplete>
								<md-field>
									<label for="rule-headerValue">{{t('headerValue')}}</label>
									<md-input id="rule-headerValue" v-model="edit.headerValue" />
								</md-field>
							</div>
							<md-field v-show="edit.execType == 1">
								<label for="rule-code">{{t('code')}}</label>
								<md-textarea id="rule-code" v-model="edit.code"></md-textarea>
							</md-field>
						</md-card-content>
					</md-card>
				</div>
				<div class="md-layout-item md-size-30 md-small-size-100 edit-right">
					<md-card>
						<md-card-header>
							<md-card-header-text>
								<div class="md-title">{{t('test_url')}}</div>
							</md-card-header-text>
						</md-card-header>
						<md-card-content>
							<md-field>
								<label for="rule-test"></label>
								<md-input id="rule-test" v-model="edit.test" />
							</md-field>
							<code>{{testResult}}</code>
						</md-card-content>
					</md-card>
					<md-card>
						<md-card-header>
							<md-card-header-text>
								<div class="md-title">{{t('save')}}</div>
							</md-card-header-text>
						</md-card-header>
						<md-card-content>
							<p class="group">
								<span>{{t('group')}}</span><span>{{edit.group}}</span>
								<md-button class="md-primary" @click="onEditChooseGroup">{{t('choose')}}</md-button>
							</p>
							<md-button class="md-raised md-primary" @click="saveRule">{{t('save')}}</md-button>
						</md-card-content>
					</md-card>
				</div>
			</div>
		</div>
		<div class="float-button">
			<md-speed-dial class="md-bottom-right" md-event="click">
				<md-speed-dial-target class="md-primary" :title="t('batch_mode')" @click="onBatchEnter">
					<md-icon v-show="isBatch" class="iconfont icon-exit-to-app"></md-icon>
					<md-icon v-show="!isBatch" class="iconfont icon-playlist-add-check"></md-icon>
				</md-speed-dial-target>
				<md-speed-dial-content>
					<md-button class="md-icon-button" :title="t('select_all')" @click="onBatchAll">
						<md-icon class="iconfont icon-done-all"></md-icon>
					</md-button>
					<md-button class="md-icon-button" :title="t('enable')" @click="onBatchEnable">
						<md-icon class="iconfont icon-touch-app"></md-icon>
					</md-button>
					<md-button class="md-icon-button" :title="t('group')" @click="onBatchGroup">
						<md-icon class="iconfont icon-playlist-add"></md-icon>
					</md-button>
					<md-button class="md-icon-button" :title="t('share')" @click="onBatchShare">
						<md-icon class="iconfont icon-share"></md-icon>
					</md-button>
					<md-button class="md-icon-button" :title="t('delete')" @click="onBatchDelete">
						<md-icon class="iconfont icon-delete"></md-icon>
					</md-button>
				</md-speed-dial-content>
			</md-speed-dial>
			<md-button class="md-fab md-primary" @click="showAddPage">
				<md-icon class="iconfont icon-add"></md-icon>
			</md-button>
		</div>
		<div class="drags">
			<md-card class="rule-drag-view dragbox" v-for="r of dragable_rule" :key="r._v_key">
				<md-card-area>
					<md-card-header>
						<div class="md-title">{{r.name}}</div>
					</md-card-header>
					<md-card-content>
						<p>{{t('matchType')}}: {{t('match_' + r.matchType)}}</p>
						<p>{{t('matchRule')}}: {{r.pattern}}</p>
						<p>{{t('exec_type')}}: {{t('exec_' + (r.isFunction ? 'function' : 'normal'))}}</p>
						<p v-if="r.ruleType === 'redirect'">{{t('redirectTo')}}: {{r.to}}</p>
						<p v-if="(r.ruleType === 'modifySendHeader' || r.ruleType === 'modifyReceiveHeader') && !r.isFunction">{{t('headerName')}}: {{r.action.name}}</p>
						<p v-if="(r.ruleType === 'modifySendHeader' || r.ruleType === 'modifyReceiveHeader') && !r.isFunction">{{t('headerValue')}}: {{r.action.value}}</p>
						<pre v-if="r.isFunction">{{r.code}}</pre>
					</md-card-content>
				</md-card-area>
				<md-card-actions md-alignment="left">
					<md-button class="md-icon-button" @mousedown="e => onDragStart(e, r)" @touchstart="e => onDragStart(e, r)" style="cursor:move"><md-icon class="iconfont icon-open-with"></md-icon></md-button>
					<md-button class="md-primary" @click="dragable_rule.splice(dragable_rule.indexOf(r), 1)">{{t('cancel')}}</md-button>
				</md-card-actions>
			</md-card>
		</div>
		<md-dialog :md-active.sync="cloud.show" class="cloud-dialog" :md-fullscreen="false">
			<md-dialog-title>{{t('cloud_backup')}}</md-dialog-title>
			<md-dialog-content>
				<p v-if="cloud.has">{{t("cloud_backup_at", cloudDate)}}</p>
				<p v-else>{{t("cloud_no_backup")}}</p>
				<p>
					<md-button class="with-icon" @click="cloudUpload"><md-icon class="iconfont icon-cloud-upload"></md-icon>{{t("upload")}}</md-button>
					<md-button class="with-icon" @click="cloudDownload"><md-icon class="iconfont icon-cloud-download" :disabled="!cloud.has"></md-icon>{{t("download")}}</md-button>
					<md-button class="with-icon" @click="cloudRemove"><md-icon class="iconfont icon-delete" :disabled="!cloud.has"></md-icon>{{t('delete')}}</md-button>
				</p>
			</md-dialog-content>
			<md-dialog-actions>
				<md-button @click="onOpenCloudHelp" class="md-primary with-icon"><md-icon class="iconfont icon-open-in-new"></md-icon>{{t('help')}}</md-button>
				<md-button class="md-primary" @click="cloud.show = false">Close</md-button>
			</md-dialog-actions>
		</md-dialog>
		<md-dialog :md-active.sync="isChooseGroup" class="group-dialog" :md-fullscreen="false">
			<md-dialog-title>{{t('group')}}</md-dialog-title>
			<md-dialog-content>
				<md-list>
					<md-list-item v-for="g of groupList" :key="g">
						<md-radio v-model="choosenGroup" :value="g" />
						<span class="md-list-item-text">{{g}}</span>
					</md-list-item>
					<md-list-item class="md-radio-input new">
						<md-radio v-model="choosenGroup" value="_new" />
						<md-field md-inline>
							<label>{{t('add')}}</label>
							<md-input v-model="choosenNewGroup"></md-input>
						</md-field>
					</md-list-item>
				</md-list>
			</md-dialog-content>
			<md-dialog-actions>
				<md-button class="md-primary" @click="onChooseCancel">Close</md-button>
				<md-button class="md-primary" @click="onChooseOK">OK</md-button>
			</md-dialog-actions>
		</md-dialog>
		<md-dialog-alert :md-active.sync="alert.show" :md-content="alert.text" md-confirm-text="OK" />
		<md-snackbar md-position="center" :md-duration="4000" :md-active.sync="toast.show" md-persistent>{{toast.text}}</md-snackbar>
	</div>
</template>

<script>
import browser from 'webextension-polyfill';
import merge from 'merge';
import dateFormat from 'dateformat';
import utils from '../core/utils';
import rules from '../core/rules';
import file from '../core/file';
import storage from '../core/storage';
import browserSync from '../core/browserSync';

const commonHeader = require('./headers.json');
//https://github.com/inexorabletash/text-encoding/blob/3f330964c0e97e1ed344c2a3e963f4598610a7ad/lib/encoding.js#L342-L796
const encodingsList = require('./encodings.json');
const displayOptions = ['add-hot-link', 'manage-collapse-group', 'exclude-he', 'show-common-header', 'include-headers', 'modify-body'];
const initOptions = {};
displayOptions.forEach(it => initOptions[it] = false);

export default {
	data() {
		return {
			isSupportStreamFilter: utils.IS_SUPPORT_STREAM_FILTER,
			isLoadingRules: true,
			isShowEdit: false,
			isBatch: false,
			isChooseGroup: false,
			editTitle: utils.t('add'),
			encodingsList,
			edit: {
				id: -1,
				name: "",
				ruleType: "cancel",
				ruleTypeEditable: true,
				matchType: "all",
				matchRule: "",
				excludeRule: "",
				encoding: encodingsList[0],
				redirectTo: "",
				headerName: "",
				headerValue: "",
				execType: 0,
				code: "",
				test: "",
				oldGroup: "",
				group: utils.t('ungrouped')
			},
			options: initOptions,
			download: {
				url: "",
				log: []
			},
			activeTab: 0,
			group: {},
			choosenGroup: "",
			choosenNewGroup: "",
			dragable_rule: [],
			alert: {
				show: false,
				text: ""
			},
			toast: {
				show: false,
				text: ""
			},
			imports: {
				status: 0,
				group_type: 0,
				group_name: "",
				list: []
			},
			batch: [],
			cloud: {
				show: false,
				has: false,
				time: null
			}
		};
	},
	computed: {
		commonHeader() {
			return this.options['show-common-header'] ? commonHeader : {
				request: [],
				response: []
			};
		},
		cloudDate() {
			return dateFormat(new Date(this.cloud.time), 'yyyy-mm-dd HH:MM:ss');
		},
		testResult() {
			if (this.edit.test === "") {
				return "";
			}
			return (data => {
				let isMatch = 0;
				switch (data.matchType) {
					case 'all':
						isMatch = 1;
						break;
					case 'regexp':
						try {
							const reg = new RegExp(data.matchRule, 'g');
							isMatch = reg.test(data.test) ? 1 : 0;
						} catch (e) {
							isMatch = -1;
						}
						break;
					case 'prefix':
						isMatch = data.test.indexOf(data.matchRule) === 0 ? 1 : 0;
						break;
					case 'domain':
						isMatch = utils.getDomain(data.test) === data.matchRule ? 1 : 0;
						break;
					case 'url':
						isMatch = data.test === data.matchRule ? 1 : 0;
						break;
					default:
						break;
				}
				if (isMatch === 1 && typeof(data.matchRule) === 'string' && data.excludeRule.length > 0) {
					try {
						const reg = new RegExp(data.excludeRule);
						isMatch = reg.test(data.test) ? 2 : 1;
					} catch (e) {
						isMatch = 1;
					}
				}
				if (isMatch === -1) {
					return utils.t('test_invalid_regexp');
				} else if (isMatch === 0) {
					return utils.t('test_mismatch');
				} else if (isMatch === 2) {
					return utils.t('test_exclude');
				}
				if (data.execType == 1) {
					return utils.t('test_custom_code');
				} else {
					// if this is a redirect rule, show the result
					if (data.ruleType === 'redirect') {
						let redirect = '';
						if (data.matchType === 'regexp') {
							redirect = data.test.replace(new RegExp(data.matchRule, 'g'), data.redirectTo);
						} else {
							redirect = data.redirectTo;
						}
						if (/^(http|https|ftp|file)%3A/.test(redirect)) {
							redirect = decodeURIComponent(redirect);
						}
						return redirect;
					} else {
						return 'Matched';
					}
				}
			})(this.edit);
		},
		groupList: function() {
			return Object.keys(this.group);
		}
	},
	methods: {
		t: utils.t,
		loadRules() {
			const _this = this;
			let finish_count = 0;
			this.isLoadingRules = true;
			this.group = {};
			this.$set(this.group, utils.t('ungrouped'), {
				name: utils.t('ungrouped'),
				collapse: this.options['manage-collapse-group'],
				rule: {}
			});
			function appendRule(table, response) {
				response.forEach(item => {
					if (typeof(_this.group[item.group]) === "undefined") {
						_this.$set(_this.group, item.group, {
							name: item.group,
							collapse: _this.options['manage-collapse-group'],
							rule: {}
						});
					}
					item["_v_key"] = table + '-' + item.id;
					_this.$set(_this.group[item.group].rule, item["_v_key"], item);
				});
				if (++finish_count >= 3) {
					_this.isLoadingRules = false;
				}
			}
			function checkResult(table, response) {
				if (!response) { // Browser is starting up
					requestRules(table);
					return;
				}
				appendRule(table, response);
			}
			function requestRules(table) {
				setTimeout(() => {
					checkResult(table, rules.get(table));
				}, 20);
			}
			utils.TABLE_NAMES.forEach(t => requestRules(t));
		},
		showAlert(text) {
			this.alert.text = text;
			this.alert.show = true;
		},
		showToast(text) {
			this.toast.text = text;
			this.toast.show = true;
		},
		onChooseCancel() {
			this.choosenNewGroup = "";
			this.choosenGroup = "";
			this.isChooseGroup = false;
		},
		onChooseOK() {
			this.isChooseGroup = false;
		},
		chooseGroup(name) {
			const _this = this;
			return new Promise(resolve => {
				_this.choosenNewGroup = "";
				_this.choosenGroup = name ? name : utils.t('ungrouped');
				_this.isChooseGroup = true;
				let _t = setInterval(() => {
					if (_this.isChooseGroup === false) {
						clearInterval(_t);
						_t = null;
						if (_this.choosenGroup === '_new') {
							if (_this.choosenNewGroup === "") {
								resolve(null);
							}
							if (typeof(_this.group[_this.choosenNewGroup]) === "undefined") {
								_this.$set(_this.group, _this.choosenNewGroup, {
									name: _this.choosenNewGroup,
									collapse: storage.prefs.get('manage-collapse-group'),
									rule: {}
								});
							}
							resolve(_this.choosenNewGroup);
						} else {
							resolve(_this.choosenGroup === "" ? null : _this.choosenGroup);
						}
					}
				}, 100);
			});
		},
		// Show add page
		showAddPage() {
			this.editTitle = utils.t('add');
			this.isShowEdit = true;
		},
		closeEditPage() {
			this.isShowEdit = false;
			this.edit.id = -1;
			this.edit.name = "";
			this.edit.ruleType = "cancel";
			this.edit.ruleTypeEditable = true;
			this.edit.matchType = "all";
			this.edit.matchRule = "";
			this.edit.excludeRule = "";
			this.edit.encoding = encodingsList[0];
			this.edit.redirectTo = "";
			this.edit.headerName = "";
			this.edit.headerValue = "";
			this.edit.execType = 0;
			this.edit.code = "";
			this.edit.test = "";
			this.edit.oldGroup = "";
			this.edit.group = utils.t('ungrouped');
		},
		saveRule() {
			if (!utils.IS_SUPPORT_STREAM_FILTER && this.edit.ruleType === 'modifyReceiveBody'){
				return;
			}
			const data = {
				"enable": true,
				"name": this.edit.name,
				"ruleType": this.edit.ruleType,
				"matchType": this.edit.matchType,
				"pattern": this.edit.matchRule,
				"exclude": this.edit.excludeRule,
				"group": this.edit.group,
				"isFunction": this.edit.execType == 1
			};
			const table = utils.getTableName(data.ruleType);
			if (data.group === '') {
				data.group = utils.t('ungrouped');
			}
			if (data.name === '') {
				this.showAlert(utils.t('name_empty'));
				return;
			}
			if (data.matchType !== 'all' && data.matchRule === '') {
				this.showAlert(utils.t('match_rule_empty'));
				return;
			}
			if (data.ruleType === 'modifyReceiveBody') {
				data.encoding = this.edit.encoding;
			}
			if (data.isFunction) {
				data.code = this.edit.code;
				if (data.code === '') {
					this.showAlert(utils.t('code_empty'));
					return;
				}
				// test code
				try {
					new Function('val', 'detail', data.code);
				} catch (e) {
					this.showAlert(e.message);
					return;
				}
			} else {
				if (data.ruleType === 'redirect') {
					if (this.edit.redirectTo === '') {
						this.showAlert(utils.t('redirect_empty'));
						return;
					}
					data.action = 'redirect';
					data.to = this.edit.redirectTo;
				}
				if ((this.edit.ruleType === 'modifySendHeader' || this.edit.ruleType === 'modifyReceiveHeader')) {
					if (this.edit.headerName === '') {
						this.showAlert(utils.t('header_empty'));
						return;
					}
					data.action = {
						"name": this.edit.headerName,
						"value": this.edit.headerValue
					};
				}
			}
			//make save data
			if (this.edit.ruleType === 'cancel') {
				data.action = 'cancel';
			}
			if (this.edit.id !== -1) {
				data.id = this.edit.id;
			}
			rules.save(table, data).then(response => {
				const v_key = table + '-' + response.id;
				if (this.edit.id && this.edit.id !== -1) {
					// Move group if required
					if (this.edit.oldGroup != data.group) {
						this.$delete(this.group[data.group].rule, v_key);
					}
				}
				if (!this.group[response.group]) {
					this.$set(this.group, response.group, {
						name: response.group,
						collapse: storage.prefs.get('manage-collapse-group'),
						rule: {}
					});
				}
				response._v_key = v_key;
				this.$set(this.group[response.group].rule, v_key, response);
				browser.runtime.sendMessage({"method": "updateCache", "type": table});
				this.showToast(utils.t('saved'));
				this.closeEditPage();
			});
		},
		onEditRule(rule) {
			if (!utils.IS_SUPPORT_STREAM_FILTER && rule.ruleType === 'modifyReceiveBody'){
				return;
			}
			this.edit.id = rule.id;
			this.edit.name = rule.name;
			this.edit.ruleType = rule.ruleType;
			this.edit.ruleTypeEditable = false;
			this.edit.matchType = rule.matchType;
			this.edit.matchRule = rule.pattern;
			this.edit.excludeRule = rule.exclude;
			this.edit.encoding = rule.encoding || "UTF-8";
			this.edit.redirectTo = rule.to || "";
			this.edit.headerName = (rule.action && rule.action.name) ? rule.action.name : "";
			this.edit.headerValue = (rule.action && rule.action.value) ? rule.action.value : "";
			this.edit.execType = rule.isFunction ? 1 : 0;
			this.edit.code = rule.code || "";
			this.edit.oldGroup = rule.group;
			this.edit.group = rule.group;
			this.editTitle = utils.t('edit');
			this.isShowEdit = true;
		},
		onCloneRule(r) {
			const newName = window.prompt(utils.t('name'), r.name + "_clone");
			if (newName) {
				const newRule = merge(true, r);
				const tableName = utils.getTableName(r.ruleType);
				newRule.name = newName;
				delete newRule["id"];
				rules.save(tableName, newRule)
				.then(res => {
					browser.runtime.sendMessage({"method": "updateCache", "type": tableName});
					this.$set(this.group[res.group].rule, tableName + '-' + res.id, res);
				})
			}
		},
		onViewRule(r) {
			if (!this.dragable_rule.includes(r)) {
				this.dragable_rule.push(r);
			}
		},
		onEditChooseGroup() {
			this.chooseGroup(this.edit.group)
			.then(r => {
				if (r !== null) {
					this.edit.group = r;
				}
			});
		},
		onRemoveRule(r, fromBatch) {
			const table = utils.getTableName(r.ruleType);
			const key = table + '-' + r.id;
			// remove from other
			if (this.batch.includes(r) && !fromBatch) {
				this.batch.splice(this.batch.indexOf(r), 1);
			}
			if (this.dragable_rule.includes(r)) {
				this.dragable_rule.splice(this.dragable_rule.indexOf(r), 1);
			}
			rules.remove(table, r.id).then(response => {
				browser.runtime.sendMessage({"method": "updateCache", "type": table});
				Object.keys(this.group).forEach(e => {
					if (typeof(this.group[e].rule[key]) !== "undefined") {
						this.$delete(this.group[e].rule, key);
					}
				});
			});
		},
		// Enable or disable a rule
		onRuleEnable(rule, newValue) {
			const table = utils.getTableName(rule.ruleType);
			rules.save(table, rule).then(response => {
				browser.runtime.sendMessage({"method": "updateCache", "type": table});
			});
		},
		changeRuleGroup(rule, newGroup) {
			const _this = this;
			return new Promise(resolve => {
				const table = utils.getTableName(rule.ruleType);
				const oldGroup = rule.group;
				_this.$delete(_this.group[oldGroup].rule, table + '-' + rule.id);
				rule.group = newGroup;
				_this.$set(_this.group[newGroup].rule, table + '-' + rule.id, rule);
				rules.save(table, rule).then(function(response) {
					resolve(response);
				});
			});
		},
		onChangeRuleGroup(rule) {
			this.chooseGroup()
			.then(r => {
				if (r !== null) {
					this.changeRuleGroup(rule, r);
				}
			});
		},
		onGroupRename(g) {
			const name = window.prompt(utils.t('enter_group_name'), g.name);
			if (name) {
				const queue = [];
				Object.values(g.rule).forEach(r => {
					r.group = name;
					queue.push(rules.save(utils.getTableName(r.ruleType), r));
				});
				Promise.all(queue).then(() => {
					g.name = name;
				});
			}
		},
		onGroupShare(name) {
			const result = {};
			utils.TABLE_NAMES.forEach(t => {
				result[t] = [];
			});
			Object.values(this.group[name].rule).forEach(e => {
				result[utils.getTableName(e.ruleType)].push(e);
			});
			file.save(
				JSON.stringify(rules.createExport(result), null, "\t"),
				utils.getExportName(name)
			);
		},
		onGroupDelete(name) {
			// Delete group, but not delete rules, put all rules to "ungrouped"
			const ungrouped = utils.t('ungrouped');
			if (name === ungrouped) {
				return;
			}
			Object.values(this.group[name].rule).forEach(e => {
				this.changeRuleGroup(e, ungrouped);
			});
			this.$delete(this.group, name);
		},
		onExportAll() {
			const result = {};
			utils.TABLE_NAMES.forEach(k => {
				result[k] = rules.get(k);
			});
			file.save(
				JSON.stringify(rules.createExport(result), null, "\t"),
				utils.getExportName(name)
			);
		},
		onImport() {
			file.load('.json').then(content => {
				this.showImportConfirm(content);
			});
		},
		onImportRuleChooseGroup(rule) {
			this.chooseGroup(rule.group)
			.then(r => {
				if (r !== null) {
					rule.group = r;
				}
			});
		},
		onImportChooseGroup() {
			this.chooseGroup(this.imports.group_name)
			.then(r => {
				if (r !== null) {
					this.imports.group_name = r;
				}
			});
		},
		onImportSave() {
			this.imports.status = 1;
			const queue = [];
			this.imports.list.forEach(e => {
				//不导入
				if (e.import_action == 3) {
					return;
				}
				if (e.import_action == 2) {
					e.id = e.import_old_id;
				} else {
					delete e["id"];
				}
				delete e["import_action"];
				delete e["import_old_id"];
				const tableName = utils.getTableName(e.ruleType);
				e.group = this.imports.group_type === 0 ? this.imports.group_name : e.group;
				if (typeof(e.enable) === "undefined") {
					e.enable = true;
				}
				queue.push(rules.save(tableName, e));
			});
			Promise.all(queue)
			.then(() => browser.runtime.sendMessage({"method": "updateCache", "type": 'all'}))
			.then(() => {
				this.imports.status = 0;
				this.showToast(utils.t('import_success'));
				const t = setTimeout(() => {
					this.loadRules();
					clearTimeout(t);
				}, 300);
			});
		},
		showImportConfirm(content) {
			this.imports.status = 1;
			this.imports.group_name = utils.t('ungrouped');
			try {
				this.imports.list = [];
				const list = typeof content === "string" ? rules.fromJson(content) : content;
				utils.TABLE_NAMES.forEach(tableName => {
					if (!list[tableName]) {
						return;
					}
					list[tableName].forEach(e => {
						if (!e.group) {
							e.group = utils.t('ungrouped');
						}
						e.id = Math.random();
						const rule = rules.get(tableName, { "name": e.name });
						e.import_action = 1;
						if (rule.length) {
							e.import_action = 2;
							e.import_old_id = rule[0].id;
						}
						this.imports.list.push(e);
					});
				});
			} catch (e) {
				console.log(e);
				this.imports.status = 0;
				return;
			}
			this.imports.status = 2;
		},
		onDownloadClick() {
			this.imports.status = 1;
			if (!this.download.log.includes(this.download.url)) {
				this.download.log.push(this.download.url);
			}
			utils.fetchUrl({
				url: this.download.url
			})
			.then(r => {
				this.showImportConfirm(r);
				this.download.url = "";
			})
			.catch(e => {
				this.showToast(e.message);
				this.imports.status = 0;
			});
		},
		onDownloadLogClick(url) {
			this.imports.status = 1;
			utils.fetchUrl({
				url: url
			})
			.then(r => {
				this.showImportConfirm(r);
			})
			.catch(e => {
				this.showToast(e.message);
				this.imports.status = 0;
			});
		},
		onDragStart(e, r) {
			const isTouch = typeof(TouchEvent) !== "undefined" && e instanceof TouchEvent;
			const box = (el => {
				let p = el;
				while (p = p.parentElement) {
					if (p.classList.contains('dragbox')) {
						return p;
					}
				}
			})(e.currentTarget);
			const offset = (el => {
				const rect = el.getBoundingClientRect();
				return {
					top: rect.top,
					left: rect.left
				};
			})(box);
			const last = {
				x: e.pageX || e.touches[0].pageX,
				y: e.pageY || e.touches[0].pageY
			};
			let end = false;
			if (isTouch) {
				function onTouchMove(e) {
					offset.top += e.touches[0].pageY - last.y;
					last.y = e.touches[0].pageY;
					offset.left += e.touches[0].pageX - last.x;
					last.x = e.touches[0].pageX;
					e.stopPropagation();
					e.preventDefault();
				}
				document.body.addEventListener('touchmove', onTouchMove, { passive: false });
				document.body.addEventListener('touchend', () => {
					end = true;
					document.body.removeEventListener('mousemove', onTouchMove);
				});
				document.body.addEventListener('touchcancel', () => {
					end = true;
					document.body.removeEventListener('mousemove', onTouchMove);
				});
			} else {
				function onMouseMove(e) {
					offset.top += e.pageY - last.y;
					last.y = e.pageY;
					offset.left += e.pageX - last.x;
					last.x = e.pageX;
				}
				document.body.addEventListener('mousemove', onMouseMove);
				document.body.addEventListener('mouseup', () => {
					end = true;
					document.body.removeEventListener('mousemove', onMouseMove);
				});
			}
			function setNewOffset() {
				box.style.top = offset.top + "px";
				box.style.left = offset.left + "px";
				if (!end) requestAnimationFrame(setNewOffset);
			}
			setNewOffset();
		},
		onOpenHelp() {
			browser.runtime.sendMessage({
				method: "openURL",
				url: utils.t('url_help')
			});
		},
		onBatchEnter() {
			this.isBatch = !this.isBatch;
			this.batch = [];
		},
		onBatchAll() {
			const groups = Object.values(this.group);
			const firstRule = Object.values(groups[0].rule)[0];
			const isUnSelect = this.batch.includes(firstRule);
			if (isUnSelect) {
				groups.forEach(g => {
					Object.values(g.rule).forEach(r => {
						if (this.batch.includes(r)) {
							this.batch.splice(this.batch.indexOf(r), 1);
						}
					});
				});
			} else {
				groups.forEach(g => {
					Object.values(g.rule).forEach(r => {
						if (!this.batch.includes(r)) {
							this.batch.push(r);
						}
					});
				});
			}
		},
		onBatchSelectGroup(g) {
			const rule = Object.values(g.rule);
			if (this.batch.includes(rule[0])) {
				rule.forEach(e => {
					if (this.batch.includes(e)) {
						this.batch.splice(this.batch.indexOf(e), 1);
					}
				});
			} else {
				rule.forEach(e => {
					if (!this.batch.includes(e)) {
						this.batch.push(e);
					}
				});
			}
		},
		onBatchEnable() {
			const table = [];
			const queue = [];
			const setTo = !this.batch[0].enable;
			this.batch.forEach(rule => {
				if (rule.enable === setTo) {
					return;
				}
				rule.enable = setTo;
				const tableName = utils.getTableName(rule.ruleType);
				queue.push(rules.save(tableName, rule));
				if (!table.includes(tableName)) {
					table.push(tableName);
				}
			});
			Promise.all(queue)
			.then(() => {
				table.forEach(t => {
					browser.runtime.sendMessage({"method": "updateCache", "type": t});
				});
			});
		},
		onBatchGroup() {
			this.chooseGroup()
			.then(r => {
				if (r !== null) {
					this.batch.forEach(rule => {
						this.changeRuleGroup(rule, r);
					});
				}
			});
		},
		onBatchShare() {
			const result = {};
			utils.TABLE_NAMES.forEach(t => {
				result[t] = [];
			});
			this.batch.forEach(e => {
				result[utils.getTableName(e.ruleType)].push(e);
			});
			file.save(
				JSON.stringify(rules.createExport(result), null, "\t"),
				utils.getExportName()
			);
		},
		onBatchDelete() {
			if (!confirm(utils.t('delete_confirm'))) {
				return;
			}
			this.batch.forEach(e => this.onRemoveRule(e, true));
			this.batch = [];
		},
		cloudUpload() {
			const result = {};
			utils.TABLE_NAMES.forEach(k => {
				result[k] = rules.get(k);
			});
			browserSync.save(rules.createExport(result)).then(e => {
				browserSync.getMeta().then(r => {
					if (r && r.time) {
						this.cloud.time = r.time;
						this.cloud.has = true;
					}
				});
			})
			.catch(e => {
				alert(utils.t("cloud_over_limit"));
			})
		},
		cloudDownload() {
			this.imports.status = 1;
			this.cloud.show = false;
			browserSync.getContent().then(r => {
				this.showImportConfirm(r)
			});
		},
		cloudRemove() {
			browserSync.clear().then(() => {
				this.cloud.has = false;
				this.cloud.time = null;
			})
		},
		onOpenCloudHelp() {
			browser.runtime.sendMessage({
				method: "openURL",
				url: utils.t('url_cloud_backup')
			});
		},
		onOpenThirdParty() {
			browser.runtime.sendMessage({
				method: "openURL",
				url: utils.t('url_third_party_rules')
			});
		}
	},
	created() {
		// Load download history
		storage.getLocal().get('dl_history').then(r => {
			if (r.dl_history !== undefined) {
				this.$set(this.download, 'log', r.dl_history);
			}
			this.$watch('download.log', newDl => {
				storage.getLocal().set({
					dl_history: newDl
				});
			});
		});
		browserSync.getMeta().then(r => {
			if (r && r.time) {
				this.cloud.time = r.time;
				this.cloud.has = true;
			}
		});
		storage.prefs.onReady().then(prefs => {
			displayOptions.forEach(it => {
				this.$set(this.options, it, prefs.get(it));
			});
			this.loadRules();
			this.$watch('options', newOpt => {
				displayOptions.forEach(it => {
					storage.prefs.set(it, newOpt[it]);
				});
			}, { deep: true });
		});
		// init anti-hot-link
		const query = (() => {
			const params = {};
			const urlParts = location.href.split("?", 2);
			if (urlParts.length == 1) {
				return params;
			}
			urlParts[1].split("&").forEach(keyValue => {
				const splitKeyValue = keyValue.split("=", 2);
				params[decodeURIComponent(splitKeyValue[0])] = decodeURIComponent(splitKeyValue[1]);
			});
			return params;
		})();
		if (query.action && query.action === "add-anti-hot-link") {
			this.edit.id = -1;
			this.edit.name = "";
			this.edit.ruleType = 'modifySendHeader';
			this.edit.ruleTypeEditable = true;
			this.edit.matchType = 'domain';
			this.edit.matchRule = utils.getDomain(query.url);
			this.edit.headerName = "referer";
			this.edit.headerValue = "";
			this.edit.execType = 0;
			this.edit.group = utils.t('ungrouped');
			this.editTitle = utils.t('add');
			this.isShowEdit = true;
		}
	},
	watch: {
		isShowEdit(newVal, oldVal) {
			if (newVal) {
				document.body.style.overflow = "hidden";
			} else {
				document.body.style.overflow = "auto";
			}
		},
		isBatch(newVal, oldVal) {
			if (newVal) {
				document.body.classList.add('batch-on');
			} else {
				document.body.classList.remove('batch-on');
			}
		},
		'edit.ruleType' (newVal, oldVal){
			if(newVal === 'modifyReceiveBody'){
				this.edit.execType = 1;
			}else if(!this.edit.code){
				this.edit.execType = 0;
			}
		}
	}
}
</script>
